/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.atteo.moonshine.atomikos;
import java.lang.management.ManagementFactory;
import java.util.Properties;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.jms.ConnectionFactory;
import javax.jms.XAConnectionFactory;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.atteo.config.XmlDefaultValue;
import org.atteo.moonshine.jta.JtaConnectionFactoryWrapper;
import org.atteo.moonshine.jta.JtaDataSourceWrapper;
import org.atteo.moonshine.jta.JtaService;
import org.atteo.moonshine.jta.PoolOptions;
import com.atomikos.icatch.SysException;
import com.atomikos.icatch.config.UserTransactionServiceImp;
import com.atomikos.icatch.config.imp.AbstractUserTransactionServiceFactory;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.icatch.standalone.UserTransactionServiceFactory;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.atomikos.jms.AtomikosConnectionFactoryBean;
import com.google.inject.Module;
import com.google.inject.PrivateModule;
import com.google.inject.Provider;
/**
* Atomikos JTA implementation.
* <p>
* Note: Atomikos can be started only once in the same JVM. This is because of limitation
* of the Atomikos itself. (It used static fields in {@link com.atomikos.icatch.system.Configuration} class.)
* You will get {@link IllegalStateException} when started the second time.
* </p>
*/
@XmlRootElement(name = "atomikos")
public class Atomikos extends JtaService {
/**
* Specifies the maximum number of active transactions.
* <p>
* A negative value means infinite amount. You will get an IllegalStateException
* with error message "Max number of active transactions reached" if you call
* {@link UserTransaction#begin()} while there are already n concurrent transactions running,
* n being this value.
* </p>
*/
@XmlElement
private Integer maxActiveTransactions = -1;
@XmlElement
@XmlDefaultValue("${dataHome}/atomikos/logs")
private String logDirectory;
@XmlElement
@XmlDefaultValue("${dataHome}/atomikos/")
private String consoleOutputDirectory;
/**
* The default timeout (in seconds) that is set for transactions when no timeout is specified.
*/
@XmlElement
@XmlDefaultValue("60")
private Integer transactionTimeout;
private UserTransactionManager manager;
private UserTransactionServiceImp service;
private static boolean initialized = false;
private synchronized static void turnOn() {
if (initialized) {
throw new IllegalStateException("Atomikos cannot be started two times in the same JVM. Use BTM instead.");
}
initialized = true;
}
private synchronized static void turnOff() {
initialized = false;
}
public class ManagerProvider implements Provider<UserTransactionManager> {
@Override
public UserTransactionManager get() {
turnOn();
System.setProperty(UserTransactionServiceImp.NO_FILE_PROPERTY_NAME, "true");
System.setProperty(UserTransactionServiceImp.HIDE_INIT_FILE_PATH_PROPERTY_NAME,
"true");
System.setProperty("com.atomikos.icatch.service",
UserTransactionServiceFactory.class.getCanonicalName());
Properties properties = new Properties();
properties.setProperty(AbstractUserTransactionServiceFactory.MAX_ACTIVES_PROPERTY_NAME,
Integer.toString(maxActiveTransactions));
String tmName = "TM_" + ManagementFactory.getRuntimeMXBean().getName();
if (tmName.length() > 30) {
tmName = tmName.substring(0, 30);
}
properties.setProperty(AbstractUserTransactionServiceFactory.TM_UNIQUE_NAME_PROPERTY_NAME, tmName);
properties.setProperty(AbstractUserTransactionServiceFactory.LOG_BASE_NAME_PROPERTY_NAME,
"log_");
properties.setProperty(AbstractUserTransactionServiceFactory.LOG_BASE_DIR_PROPERTY_NAME,
logDirectory);
properties.setProperty(AbstractUserTransactionServiceFactory.OUTPUT_DIR_PROPERTY_NAME,
consoleOutputDirectory);
properties.setProperty(AbstractUserTransactionServiceFactory.THREADED_2PC_PROPERTY_NAME,
"false");
properties.setProperty(AbstractUserTransactionServiceFactory.DEFAULT_JTA_TIMEOUT_PROPERTY_NAME,
Integer.toString(transactionTimeout * 1000));
service = new UserTransactionServiceImp(properties);
try {
service.init();
} catch (SysException e) {
throw new RuntimeException(e.getErrors().pop().toString(), e);
}
manager = new UserTransactionManager();
manager.setStartupTransactionService(false);
try {
manager.init();
} catch (SystemException e) {
throw new RuntimeException(e);
}
return manager;
}
}
private static class AtomikosDataSourceWrapper implements JtaDataSourceWrapper {
@Inject
UserTransactionManager userTransactionManager;
@Override
public DataSource wrap(String name, XADataSource xaDataSource, PoolOptions poolOptions, String testQuery) {
AtomikosDataSourceBean wrapped = new AtomikosDataSourceBean();
wrapped.setXaDataSource(xaDataSource);
wrapped.setUniqueResourceName(name);
if (poolOptions == null) {
poolOptions = new PoolOptions();
}
if (poolOptions.getMaxLifeTime() != null && poolOptions.getMaxLifeTime() != 0) {
wrapped.setMaxLifetime(poolOptions.getMaxLifeTime());
} else {
// test query is only needed when we don't know how long Atomikos can keep connections in the pool
wrapped.setTestQuery(testQuery);
}
if (poolOptions.getMinPoolSize() != null) {
wrapped.setMinPoolSize(poolOptions.getMinPoolSize());
}
if (poolOptions.getMaxPoolSize() != null) {
wrapped.setMaxPoolSize(poolOptions.getMaxPoolSize());
}
if (poolOptions.getMaxIdleTime() != null) {
wrapped.setMaxIdleTime(poolOptions.getMaxIdleTime());
}
if (poolOptions.getReapTimeout() != null) {
wrapped.setReapTimeout(poolOptions.getReapTimeout());
}
return wrapped;
}
@Override
public void close(DataSource dataSource) {
((AtomikosDataSourceBean) dataSource).close();
}
}
private static class AtomikosConnectionFactoryWrapper implements JtaConnectionFactoryWrapper {
@Override
public ConnectionFactory wrap(String name, XAConnectionFactory xaFactory,
PoolOptions poolOptions) {
AtomikosConnectionFactoryBean wrapped = new AtomikosConnectionFactoryBean();
wrapped.setXaConnectionFactory(xaFactory);
wrapped.setUniqueResourceName(name);
if (poolOptions == null) {
return wrapped;
}
if (poolOptions.getMinPoolSize() != null) {
wrapped.setMinPoolSize(poolOptions.getMinPoolSize());
}
if (poolOptions.getMaxPoolSize() != null) {
wrapped.setMaxPoolSize(poolOptions.getMaxPoolSize());
}
if (poolOptions.getMaxIdleTime() != null) {
wrapped.setMaxIdleTime(poolOptions.getMaxIdleTime());
}
if (poolOptions.getReapTimeout() != null) {
wrapped.setReapTimeout(poolOptions.getReapTimeout());
}
return wrapped;
}
@Override
public void close(ConnectionFactory connectionFactory) {
((AtomikosConnectionFactoryBean) connectionFactory).close();
}
}
@Override
public Module configure() {
return new PrivateModule() {
@Override
protected void configure() {
configureCommon(binder());
bind(UserTransactionManager.class).toProvider(new ManagerProvider()).in(Singleton.class);
bind(TransactionManager.class).to(UserTransactionManager.class);
expose(TransactionManager.class);
bind(UserTransaction.class).to(UserTransactionManager.class);
expose(UserTransaction.class);
bind(JtaDataSourceWrapper.class).to(AtomikosDataSourceWrapper.class).in(Singleton.class);
expose(JtaDataSourceWrapper.class);
bind(JtaConnectionFactoryWrapper.class).to(AtomikosConnectionFactoryWrapper.class).in(Singleton.class);
expose(JtaConnectionFactoryWrapper.class);
}
};
}
@Override
public void close() {
if (manager != null) {
manager.close();
}
if (service != null) {
service.shutdownWait();
}
turnOff();
}
}